/*
 * NodoIndice.cpp
 *
 *  Created on: 03/04/2010
 *      Author: administrador
 */

#include "NodoIndice.h"
#include "FactoryNodo.h"
#include "Arbol.h"
/*
 * Constructor que se llama cuando el Nodo no existe.
 * Se le debe asignar un id de nodo libre.
 */
NodoIndice::NodoIndice(int nivel, int idNodo, char tipo) : INodo(idNodo, tipo) {
	this -> nivel = nivel;

	this -> cantidadElementos = 0;

	this -> clavesElementos.clear();

	this -> idNodos.clear();
}

NodoIndice::~NodoIndice() {

	// TODO Auto-generated destructor stub
}

Clave NodoIndice::getClaveElemento(int posicion) {
	return this -> clavesElementos[posicion];
}

int NodoIndice::getIdNodo(int posicion) {
	return this -> idNodos[posicion];
}

void NodoIndice::agregarClaveElemento(Clave clave) {

	clavesElementos.push_back(clave);
	cantidadElementos++;
	tamanioActual += clave.getTamanio();
}

void NodoIndice::agregarIdNodo(int id) {
	idNodos.push_back(id);
	tamanioActual += sizeof(id);

}

void NodoIndice::agregarIdNodoNuevoYclaveElemento(int id, Clave clave) {
	clavesElementos.resize(cantidadElementos + 1);
	idNodos.resize(cantidadElementos + 2);

	/* Desplazo los elementos en los vectores para insertar id y clave */
	int i = cantidadElementos;
	while ((i > 0) && (clave < clavesElementos[i - 1])) {
		clavesElementos[i] = clavesElementos[i - 1];
		idNodos[i + 1] = idNodos[i];
		i--;
	}
	clavesElementos[i] = clave;
	idNodos[i + 1] = id;

	cantidadElementos++;
	
	this -> setTamanioActual(this -> getTamanioActual() + clave.getTamanio() + sizeof(id));
}

void NodoIndice::borrarIdNodoNuevoYclaveElemento(int id)
{
	int posicionId = 0;

	/* Busco la posicion en la cual esta el id a Eliminar */
	while(posicionId <= cantidadElementos)
	{
		if(id == idNodos[posicionId])
		{
			break;
		}
		posicionId++;
	}

	/* Reordeno los elementos y idNodos */
		for(int i = posicionId; i < cantidadElementos; i++)
		{
		/* Solo paso el separador si no es el primer idNodo ya que no tiene */

			if (i > 0){
				clavesElementos[i - 1] = clavesElementos[i];
			}

			idNodos[i] = idNodos[i + 1];

		}
	/* Se modifican la cantidad de elementos y el tamaño fisico y de vectores */

	int tamanioClaveBorrar = 0;
	
	if (posicionId < this->cantidadElementos){

		tamanioClaveBorrar = this->clavesElementos[posicionId].getTamanio();
	}
	else{

		tamanioClaveBorrar = this->clavesElementos[posicionId-1].getTamanio();
	}

	cantidadElementos--;

	clavesElementos.resize(cantidadElementos);

	idNodos.resize(cantidadElementos+1);

	this -> setTamanioActual(this -> getTamanioActual() - tamanioClaveBorrar - sizeof(id));

}

void NodoIndice::partirNodoEnOverflow(Clave &claveAPromover, INodo* nodoNuevo) {

	Clave claveElementoActual;
	int idNodoActual;
	int i;
	int elementoMedio = this->cantidadElementos/2;
	int cantidadElementos = this->cantidadElementos;
	for (i = elementoMedio+1; i < cantidadElementos; i++) {
		idNodoActual = this->getIdNodo(i);
		claveElementoActual = this-> getClaveElemento(i);

		((NodoIndice*) nodoNuevo) -> agregarIdNodo(idNodoActual);
		((NodoIndice*) nodoNuevo) -> agregarClaveElemento(claveElementoActual);

		this -> cantidadElementos--;
		this -> setTamanioActual(this -> getTamanioActual() - claveElementoActual.getTamanio() - sizeof(idNodoActual));
	}

	/* Falta pasar el ultimo idNodo, pues siempre hay uno mas que clavesElementos */
	idNodoActual = this -> getIdNodo(i);
	((NodoIndice*) nodoNuevo) -> agregarIdNodo(idNodoActual);

	claveAPromover = this -> getClaveElemento(elementoMedio);
	this -> cantidadElementos--;
	this -> setTamanioActual(this -> getTamanioActual() - claveAPromover.getTamanio()-sizeof(idNodoActual));
	this -> clavesElementos.resize(this -> cantidadElementos);
	this -> idNodos.resize(this -> cantidadElementos + 1);

}

bool NodoIndice::balancearNodoEnUnderflow(Clave clavePadre, INodo* nodoHermano)
{
	Clave clave;
	/* Si el tamaño de ambos supera dos veces el tamaño de Underflow
	 * entonces se supone que se pueden balancear sin producir Under en ninguno */
	if((tamanioActual + nodoHermano -> getTamanioActual()) > (2 * factorCargaUnderflow))
	{
		/* Me fijo si se fusiona con el hermano Derecho o el Izquierdo, los fusiono y luego los parto */
		if(this->fusionarNodoEnUnderflow(clavePadre,nodoHermano))
		{

			this->partirNodoEnOverflow(clave,nodoHermano);
			return true;
		}

	}

	return false;

}

bool NodoIndice::fusionarNodoEnUnderflow(Clave clavePadre,INodo* nodoHermano)
{

	/* Si la suma de los dos tamaños supera el tamañoMaximo de nodo entonces
	 * no se fusionan porque se perderian datos */
	if((tamanioActual + nodoHermano->getTamanioActual())>=(Arbol::tamanioMaximoNodo))
	{
		return false;
	}

		/* Agrego el primer par Elemento, IdNodo al nodoDefinitivo */
		this->agregarClaveElemento(clavePadre);

		/* Se fusiona con el hermano Derecho, se agregan los elementos del hermano al final del this, menos el primero y
		 * el primer id agregados anteriormente */
		int cantidadElementosHermano = nodoHermano->getCantidadElementos();
		for(int i = 0; i < cantidadElementosHermano; i++)
		{
			this->agregarIdNodo(((NodoIndice*)nodoHermano) ->getIdNodo(i));
			this->agregarClaveElemento(((NodoIndice*)nodoHermano) -> getClaveElemento(i));

		}
		this->agregarIdNodo(((NodoIndice*)nodoHermano) ->getIdNodo(cantidadElementosHermano));
		/* Se limpia el hermano */

		nodoHermano -> vaciarNodo();


	return true;

}

void NodoIndice::vaciarNodo(){

	this->cantidadElementos=0;
	this->clavesElementos.resize(0);
	this->idNodos.resize(0);
	this->tamanioActual = this->getTamanioElementosControlNodo();
}

INodo* NodoIndice::obtenerHermanoIzquierdo(INodo* nodo)
{
	/* Busco la posicion donde esta el nodo del cual quiero el hermano Izquierdo */
	int posicion = 0;
	while ((nodo->getId() != idNodos[posicion]) && (posicion < cantidadElementos+1))
	{
		posicion++;
	}

	/* Si no es el primero retorno el anterior al buscado */
	if(posicion != 0)
	{
		return FactoryNodo::factory(this->nivel, idNodos[posicion-1]);
	}

	return NULL;
}

INodo* NodoIndice::obtenerHermanoDerecho(INodo* nodo)
{
	/* Busco la posicion donde esta el nodo del cual quiero el hermano Izquierdo */
	int posicion = 0;
	while ((nodo->getId() > idNodos[posicion]) && (posicion < cantidadElementos+1))
	{
		posicion++;
	}

	/* Si no es el primero retorno el anterior al buscado */
	if(posicion != cantidadElementos)
	{
		return FactoryNodo::factory(this->nivel,idNodos[posicion+1]);
	}

	return NULL;
}

bool NodoIndice::tieneHermanoIzquierdo(INodo* nodo)
{
	if(nodo->getId() == idNodos[0])
	{
		return false;
	}

	return true;
}

bool NodoIndice::tieneHermanoDerecho(INodo* nodo)
{
	/* En cantidad elementos porque hay un elemento menos que cantidad de ids nodos */
	if(nodo->getId() == idNodos[cantidadElementos])
	{
		return false;
	}

	return true;
}

Clave NodoIndice::verificarSeparador()
{

	INodo *nodoBuscado = FactoryNodo::factory(this->nivel,this->idNodos[0]);

	Clave separador = nodoBuscado->verificarSeparador();

	delete nodoBuscado;

	return separador;

}

void NodoIndice::solucionarOverflow(INodo* nodoEnOverflow)
{
	INodo* nodoNuevo;

	nodoNuevo = FactoryNodo::factory(this->nivel);

	/* Se parte el nodoEnOverflow y los elementos de la segunda mitad van al nuevoNodo */

	Clave claveAPromover;


	nodoEnOverflow->partirNodoEnOverflow(claveAPromover,nodoNuevo);

	/* Se promueven a este nodo el par id nodoNuevo y clave elemento */

	this -> agregarIdNodoNuevoYclaveElemento(nodoNuevo -> getId(), claveAPromover);

	nodoNuevo->serializar();
	delete nodoNuevo;
}

void NodoIndice::actualizarSeparador(Clave separador, int idNodoDerechoSeparador){

	for (int i=0; i<= this->cantidadElementos ; i++){

		if (idNodoDerechoSeparador==this->idNodos[i]){

			this->clavesElementos[i-1] = separador;
			break;
		}

	}

}

void NodoIndice::solucionarUnderflow(INodo* nodoEnUnderflow)
{
	INodo* nodoIzq;
	INodo* nodoDer;

	/* Se obtiene el hermano izquierdo */
	nodoIzq = this -> obtenerHermanoIzquierdo(nodoEnUnderflow);

	if (!nodoIzq){

		nodoDer = this -> obtenerHermanoDerecho(nodoEnUnderflow);
		nodoIzq = nodoEnUnderflow;
	}
	else{

		nodoDer = nodoEnUnderflow;
	}

	Clave claveElemento = this->buscarClave(nodoIzq->getId());

	if(nodoIzq->balancearNodoEnUnderflow(claveElemento, nodoDer))
	{
		this->actualizarSeparador(nodoDer->verificarSeparador(), nodoDer->getId());

	}
	else
	{
		if(nodoIzq -> fusionarNodoEnUnderflow(claveElemento,nodoDer))
		{
	
			borrarIdNodoNuevoYclaveElemento(nodoDer->getId());
		}
	}

		nodoIzq -> serializar();
		nodoDer -> serializar();
		delete nodoIzq;
		delete nodoDer;



	/* Si todas las alternativas para solucionar el Underflow fallan entonces el nodo del cual se quito el
	 * elemento queda en Underflow */
}

Clave NodoIndice::buscarClave(int idNodoIzq){


	for (int i=0; i < this->cantidadElementos; i++){

		if (idNodoIzq==this->idNodos[i]){

			return this->clavesElementos[i];
		}
	}

	throw ElementoInexistenteException();
}

int NodoIndice::buscarNodoParaClave(Clave clave) {
	/* Busco la posicion donde deberia estar el elemento en base a las claves */
	int posicion = 0;
	while (clave >= clavesElementos[posicion]) {
		posicion++;
		if (posicion == cantidadElementos)
			break;
	}

	/* El elemento buscado deberia estar en el nodo que referencia el idNodo de la posicion encontrada */
	return idNodos[posicion];
}

bool NodoIndice::insertarElemento(Elemento* elemento) {
	INodo* nodoBuscado;
	int idNodoBuscado = buscarNodoParaClave(elemento -> getClave());
	/* Si el nivel de este nodo es 1, entonces el nodoBuscado es hoja; sino, es indice */

	nodoBuscado = FactoryNodo::factory(this->nivel,idNodoBuscado);

	/* Llamo al insertar y capturo las posibles excepciones */
	try {
		if (nodoBuscado -> insertarElemento(elemento)) {

			nodoBuscado->serializar();

		}
	}
	catch (ElementoExistenteException ex) {

		throw ElementoExistenteException(elemento -> getClave());
	}
	catch (OverflowException ex) {
//		ex.mostrarError();

		solucionarOverflow(nodoBuscado);

		nodoBuscado->serializar();
		
		delete nodoBuscado;

		/* Verificacion de OverFlow */
		if (tamanioActual >= factorCargaOverflow) {
			throw OverflowException(this -> getId());
		}

		return true;
	}

	delete nodoBuscado;

	return false;
}

bool NodoIndice::quitarElemento(Clave clave) {

	INodo* nodoBuscado;
	int idNodoBuscado = buscarNodoParaClave(clave);

	/* Si el nivel de este nodo es 1, entonces el nodoBuscado es hoja; sino, es indice */
	nodoBuscado = FactoryNodo::factory(this->nivel,	idNodoBuscado);

	/* Llamo al quitar y capturo las posibles excepciones */
	try {
		if (nodoBuscado -> quitarElemento(clave)) {
			nodoBuscado->serializar();
			/* Verifico que el separador siga siendo valido */
		}
			for (int i=0; i< this->cantidadElementos;i++){
				if (clave == this->clavesElementos[i]){

					this->actualizarSeparador(nodoBuscado->verificarSeparador(),idNodoBuscado);
				}
			}
	}
	catch (ElementoInexistenteException ex) {
		delete nodoBuscado;
		throw ElementoInexistenteException(clave);
	}
	catch (UnderflowException ex) {
		ex.mostrarError();
		solucionarUnderflow(nodoBuscado);


		/* Verificacion de UnderFlow */
		if (tamanioActual < factorCargaUnderflow) {
			throw UnderflowException(this -> getId());
		}

		return true;
	}
		delete nodoBuscado;

	return false;
}

bool NodoIndice::modificarElemento(Clave clave, string* nuevoValor)
{
	int idNodoBuscado = buscarNodoParaClave(clave);
	INodo* nodoBuscado = FactoryNodo::factory(this->nivel,idNodoBuscado);

	try
	{
		if (nodoBuscado -> modificarElemento(clave, nuevoValor))
		{
			nodoBuscado -> serializar();
		}
	}
	catch (NodoVacioException ex)
	{
		throw NodoVacioException(ex.getIdNodo());
	}
	catch (ElementoInexistenteException ex)
	{
		delete nodoBuscado;
		throw ElementoInexistenteException(ex.getElemento());
	}
	catch (OverflowException ex)
	{
		ex.mostrarError();

		solucionarOverflow(nodoBuscado);

		nodoBuscado->serializar();

		return true;
	}
	catch (UnderflowException ex)
	{
		ex.mostrarError();

		solucionarUnderflow(nodoBuscado);

		nodoBuscado -> serializar();

		return true;
	}

	delete nodoBuscado;

	return false;
}

Elemento* NodoIndice::buscarElemento(Clave clave) {

	int idNodoBuscado = buscarNodoParaClave(clave);

	INodo* nodoBuscado;

	/* Si el nivel de este nodo es 1, entonces el nodoBuscado es hoja; sino, es indice */

	nodoBuscado = FactoryNodo::factory(this->nivel,idNodoBuscado);

	Elemento* aRetornar;

	try
	{
		aRetornar = nodoBuscado -> buscarElemento(clave)->clonar();
	}
	catch (NodoVacioException ex)
	{
		delete nodoBuscado;

		throw NodoVacioException(ex.getIdNodo());
	}
	catch (ElementoInexistenteException ex)
	{
		delete nodoBuscado;

		throw ElementoInexistenteException(ex.getElemento());
	}

	delete nodoBuscado;

	return aRetornar;
}

Elemento* NodoIndice::obtenerPrimerElementoDelPrimerNodo()
{
	INodo* primerNodo = this;
	int primerId = idNodos[0];
	while(primerNodo->getNivel() != 0)
	{
		primerNodo = FactoryNodo::factory(this->nivel,primerId);
		if(primerNodo->getNivel() != 0)
		{
			primerId = ((NodoIndice*)primerNodo)->getIdNodo(0);
		}
	}

	return (((NodoHoja*)primerNodo)->getElemento(0));
}

void NodoIndice::printNodo(ofstream* archivoSalida, int cantidadNiveles) {

	string tab;

	for (int k = 0; k < cantidadNiveles - this->nivel; k++) {

		tab += '\t';
	}

	*archivoSalida << tab << "Numero Nodo "<< this->id << ", " << this->cantidadElementos << ", " << this->nivel <<" Tamaño Actual " << this->tamanioActual ;

	for (int i = 0; i < this->cantidadElementos; i++) {

		*archivoSalida << ", " << "(" << this->idNodos[i] << "), " << this->clavesElementos[i].getClave();
	}

	*archivoSalida << ", " << "(" << this->idNodos[this->cantidadElementos] << ")";

	*archivoSalida << std::endl;
	*archivoSalida << std::endl;

	for (int i = 0; i < this->cantidadElementos + 1; i++) {

		INodo *nodo;

		nodo = FactoryNodo::factory(this->nivel,this->idNodos[i]);

		nodo->printNodo(archivoSalida,cantidadNiveles);

		delete nodo;
	}

}

void NodoIndice::serializar() {

	if (this->cantidadElementos>0){

	c_bloque *bufferNodo = new c_bloque();

	bufferNodo->set_nro(this->id);

	bufferNodo->add(this->id);
	bufferNodo->add(this->cantidadElementos);
	bufferNodo->add(this->nivel);
	bufferNodo->add(this->tamanioActual);

	if (this->cantidadElementos > 0) {

		for (int i = 0; i < this->cantidadElementos; i++) {

			bufferNodo->add(this->idNodos[i]);

			this->clavesElementos[i].serializar(bufferNodo);
		}

		bufferNodo->add(this->idNodos[this->cantidadElementos]);

	}

	bufferNodo->save();

	delete bufferNodo;
	}
	else{
		Archivo::getInstance()->borrarBloque(this->id);
	}
}

void NodoIndice::hidratar() {

	if (this->id < 0) {

		this->id = Archivo::getInstance()->getBloqueLibre();

	} else {

		c_bloque *bufferNodo = new c_bloque();

		bufferNodo->set_nro(this->id);

		bufferNodo->load();

		bufferNodo->get(&(this->id));

		bufferNodo->get(&(this->cantidadElementos));

		bufferNodo->get(&(this->nivel));

		bufferNodo->get(&(this->tamanioActual));

		int descendiente;

		Clave clave;

		this->idNodos.resize(this->cantidadElementos + 1);

		this->clavesElementos.resize(this->cantidadElementos);

		for (int i = 0; i < this->cantidadElementos; i++) {

			bufferNodo->get(&descendiente);

			Clave clave;

			clave.hidratar(bufferNodo);

			this->idNodos[i] = descendiente;

			this->clavesElementos[i] = clave;

		}

		bufferNodo->get(&descendiente);

		this->idNodos[this->cantidadElementos] = descendiente;

		delete bufferNodo;

	}

}
